﻿/* HEADER
 * ------
 * © 2009 by Salomon Zwecker 
 * modified by:
 * - 
 */
using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;

namespace Shapes.Geometry
{
    /// <summary>
    /// a geometry which can not be filled (1-Dimensional)
    /// </summary>
    public abstract class Drawing : IDisposable, ITransformable2D,
#if WINDOWS
        ICloneable
#else
        Shapes.ICloneable
#endif
    {
        /// <summary>
        /// Gets the enumeration type which is mapped to this kind of object
        /// </summary>
        /// <returns>the geometry typee of this object</returns>
        public abstract GeometryType GetGeometryType();

        internal Transformation2D _Transform = new Transformation2D(Vector2.Zero, 1, 1);
        /// <summary>
        /// The transformation and bounding information of the object
        /// </summary>
        public Transformation2D Transform { get { return _Transform; } }


        /// <summary>
        /// The position of the origin in screen space coordinates
        /// </summary>
        public Vector2 Position { get { return _Transform.Position; } set { _Transform.Position = value; } }
        /// <summary>
        /// The torsion-point of the bounding in pixels (Vector2.Zero is the left upper corner)
        /// </summary>
        public Vector2 Origin { get { return _Transform._Origin; } set { _Transform.Origin = value; } }
        /// <summary>
        /// The scaling of the object
        /// </summary>
        public Vector2 Scale { get { return _Transform._Scale; } set { _Transform.Scale = value; } }
        /// <summary>
        /// The rotation of the object
        /// </summary>
        public Angle Rotation { get { return _Transform._Rotation; } set { _Transform.Rotation = value; } }

        /// <summary>
        /// the Center Position of the bounding rectangle around the geometry
        /// </summary>
        public Vector2 Center
        {
            get { return _Transform.Center; }
            set { Position = _Transform.GetPositionFromCenter(value) + _Transform._Origin; }//SetPosition(new Vector2(value.X - _Bounding._Width / 2, value.Y - _Bounding._Height / 2)); }
        }
        /// <summary>
        /// the (not transformed) width of the bounding rectangle
        /// </summary>
        public float Width { get { return _Transform._Width; } }
        /// <summary>
        /// The absolute width of the bounding rectangle (transfomation calculations included)
        /// </summary>
        public float AbsoluteWidth { get { return _Transform.Right - _Transform.Left; } }
        /// <summary>
        /// the (not transformed) height of the bounding rectangle
        /// </summary>
        public float Height { get { return _Transform._Height; } }
        /// <summary>
        /// The absolute height of the bounding rectangle (transfomation calculations included)
        /// </summary>
        public float AbsoluteHeight { get { return _Transform.Down - _Transform.Up; } }


        /// <summary>
        /// The length of the outline
        /// </summary>
        protected float _BorderLength = 0;
        /// <summary>
        /// The length of the outline
        /// </summary>
        public float BorderLength { get { return _BorderLength; } }

        /// <summary>
        /// Event is called when the geometry has changed some parameters.
        /// </summary>
        public event Action<Drawing> OnChangeGeometry;

        /// <summary>
        /// creates a drawing
        /// </summary>
        protected Drawing()
        {
        }

        /// <summary>
        /// a test to check for the distance of a point to the nearest edge
        /// </summary>
        /// <param name="point">the point to check</param>
        /// <returns>the distance to the nearest edge</returns>
        public float GetDistanceToEdge(Vector2 point)
        {
            _Transform.TransformGlobalToLocal(ref point);
            return GetDistanceToEdge(ref point);
        }
        /// <summary>
        /// a test to check for the distance of a point to the nearest edge
        /// </summary>
        /// <param name="point">the point to check</param>
        /// <param name="isLocalCoordinate">true, if the passed coordinates are in local space</param>
        /// <returns>the distance to the nearest edge</returns>
        public float GetDistanceToEdge(Vector2 point, bool isLocalCoordinate)
        {
            if (!isLocalCoordinate)
                _Transform.TransformGlobalToLocal(ref point);

            return GetDistanceToEdge(ref point);
        }
        internal abstract float GetDistanceToEdge(ref Vector2 point);


        /// <summary>
        /// returns the nearest point on the border of the geometry
        /// </summary>
        /// <param name="point">the reference point</param>
        /// <returns>the nearest point to the reference point</returns>
        public Vector2 GetNearestPointOnEdge(Vector2 point)
        {
            _Transform.TransformGlobalToLocal(ref point);
            return _Transform.TransformLocalToGlobal(GetNearestPointOnEdge(ref point));
        }
        /// <summary>
        /// returns the nearest point on the border of the geometry
        /// </summary>
        /// <param name="point">the reference point</param>
        /// <param name="isLocalCoordinate">if true, the given point is handled in local space (will not be transformed). 
        /// otherwise it is handled as parent space (global space, if it isn't attatched to another transform).</param>
        /// <returns>the nearest point to the reference point</returns>
        public Vector2 GetNearestPointOnEdge(Vector2 point, bool isLocalCoordinate)
        {
            if (!isLocalCoordinate)
                _Transform.TransformGlobalToLocal(ref point);

            return _Transform.TransformLocalToGlobal(GetNearestPointOnEdge(ref point));
        }
        internal abstract Vector2 GetNearestPointOnEdge(ref Vector2 point);

        /// <summary>
        /// Returns a position of the outline of the geometry. t is the distance from the beginning to the position.
        /// </summary>
        /// <param name="t">a value between 0 and 1</param>
        /// <returns>the position at the position</returns>
        public Vector2 GetPositionFromEdgePath(float t)
        {
            t = Math.Abs(t - (int)(t - float.Epsilon));
            return _Transform.TransformLocalToGlobal(GetPositionFromT(t));
        }
        /// <summary>
        /// Returns a position of the outline of the geometry. t is the distance from the beginning to the position.
        /// </summary>
        /// <param name="t">a value between 0 and 1</param>
        /// <param name="isLocalCoordinate">if true, the given point is handled in local space (will not be transformed). 
        /// otherwise it is handled as parent space (global space, if it isn't attatched to another transform).</param>
        /// <returns>the position at the position</returns>
        public Vector2 GetPositionFromEdgePath(float t, bool isLocalCoordinate)
        {
            t = Math.Abs(t - (int)(t - float.Epsilon));

            if (isLocalCoordinate)
                return GetPositionFromT(t);
            else
                return _Transform.TransformLocalToGlobal(GetPositionFromT(t));
        }
        internal abstract Vector2 GetPositionFromT(float t);

        /// <summary>
        /// Returns the distance from the beginning to the given point on the edge.
        /// Points which are not part of the outline may cause unexpected results.
        /// </summary>
        /// <param name="point">the point on the outline of the geometry</param>
        /// <returns>a value between 0 and 1</returns>
        public float GetEdgePathValueFromPoint(Vector2 point)
        {
            _Transform.TransformGlobalToLocal(ref point);
            return GetEdgePathValueFromPoint(ref point);
        }
        /// <summary>
        /// Returns the distance from the beginning to the given point on the edge.
        /// Points which are not part of the outline may cause unexpected results.
        /// </summary>
        /// <param name="point">the point on the outline of the geometry</param>
        /// <param name="isLocalCoordinate">if true, the given point is handled in local space (will not be transformed). 
        /// otherwise it is handled as parent space (global space, if it isn't attatched to another transform).</param>
        /// <returns>a value between 0 and 1</returns>
        public float GetEdgePathValueFromPoint(Vector2 point, bool isLocalCoordinate)
        {
            if (!isLocalCoordinate)
                _Transform.TransformGlobalToLocal(ref point);

            return GetEdgePathValueFromPoint(ref point);
        }
        internal abstract float GetEdgePathValueFromPoint(ref Vector2 point);

        /// <summary>
        /// gets a direction Vector which touches the given position witout crossing the outline at that position
        /// </summary>
        /// <param name="position">the position on the edge. should be part of the outline.</param>
        /// <returns>tangent vector</returns>
        public Vector2 GetTangent(Vector2 position)
        {
            _Transform.TransformGlobalToLocal(ref position);
            return _Transform.Rotation.RotateVector(GetTangent(ref position));
        }
        /// <summary>
        /// gets a direction Vector which touches the given position witout crossing the outline at that position
        /// </summary>
        /// <param name="position">the position on the edge. should be part of the outline.</param>
        /// <param name="isLocalCoordinate">if true, the given point is handled in local space (will not be transformed). 
        /// otherwise it is handled as parent space (global space, if it isn't attatched to another transform).</param>
        /// <returns>tangent vector</returns>
        public Vector2 GetTangent(Vector2 position, bool isLocalCoordinate)
        {
            if (!isLocalCoordinate)
                _Transform.TransformGlobalToLocal(ref position);

            return GetTangent(ref position);
        }
        internal abstract Vector2 GetTangent(ref Vector2 position);

        /// <summary>
        /// gets a direction Vector which crosses the outline at the given position with 90°
        /// </summary>
        /// <param name="position">the position on the edge. should be part of the outline.</param>
        /// <returns>normal vector</returns>
        public Vector2 GetNormal(Vector2 position)
        {
            _Transform.TransformGlobalToLocal(ref position);
            return GetNormal(ref position);
        }
        /// <summary>
        /// gets a direction Vector which crosses the outline at the given position with 90°
        /// </summary>
        /// <param name="position">the position on the edge. should be part of the outline.</param>
        /// <param name="isLocalCoordinate">if true, the given point is handled in local space (will not be transformed). 
        /// otherwise it is handled as parent space (global space, if it isn't attatched to another transform).</param>
        /// <returns>normal vector</returns>
        public Vector2 GetNormal(Vector2 position, bool isLocalCoordinate)
        {
            if (!isLocalCoordinate)
                _Transform.TransformGlobalToLocal(ref position);

            return GetNormal(ref position);
        }
        internal abstract Vector2 GetNormal(ref Vector2 position);

        /// <summary>
        /// Call this to fire the OnChange event
        /// </summary>
        protected void CallOnChange()
        {
            _Transform._IsDirty = true;

            if (OnChangeGeometry != null)
                OnChangeGeometry(this);
        }

        /// <summary>
        /// Releases all references inside of the class.
        /// </summary>
        public virtual void Dispose()
        {
            _Transform = null;
        }

        /// <summary>
        /// converts the geometry to a LineStrip. If the Geometry is a Shape, the LineStrip is getting closed
        /// </summary>
        /// <returns>a line strip object</returns>
        public virtual LineStrip ToLineStrip()
        {
            return ToLineStrip(16);
        }
        /// <summary>
        /// converts the geometry to a LineStrip. If the Geometry is a Shape, the LineStrip is getting closed
        /// </summary>
        /// <param name="vertexCount">the amount of vertices</param>
        /// <returns>a line strip object</returns>
        public LineStrip ToLineStrip(uint vertexCount)
        {
            List<Vector2> list = new List<Vector2>();

            bool close = GetGeometryType().IsShape();

            float t = (close) ? 1f / vertexCount : 1f / (vertexCount - 1);
            float pathValue = 0;

            for(int i= 0; i < vertexCount; i++)
            {
                list.Add(GetPositionFromEdgePath(pathValue));
                pathValue += t;
            }

            LineStrip strip = new LineStrip(list.ToArray());
            if (close)
                strip.Close();

            return strip;
        }

        #region ICloneable Member

        /// <summary>
        /// Clones the geometry and sets its position
        /// </summary>
        /// <param name="position">the position of the cloned object</param>
        /// <returns></returns>
        public object Clone(Vector2 position)
        {
            Drawing d = (Drawing)Clone();
            d.Position = position;
            return d;
        }
        /// <summary>
        /// Clones the geometry
        /// </summary>
        /// <returns>a clone with the same type as the geometry</returns>
        public abstract object Clone();

        #endregion    
    }
}
